//! Queue identifiers.
//!
//! These are 40 byte structures where the first 8 bytes is a selector and the
//! last 32 bytes are a real identifier.
//!
//! I just made this up on the spot because it felt like I needed it as an
//! abstraction.  I might change this in the future.

use std::fmt;

/// Length of a queue namespace.
pub const QUEUE_NS_LEN: usize = 8;

/// Length of a queue identifier.
pub const QUEUE_IDENT_LEN: usize = 32;

/// Full length of a queue reference.
pub const QUEUE_REF_LEN: usize = QUEUE_NS_LEN + QUEUE_IDENT_LEN;

/// Generalized queue identifier.
///
/// This includes both the namespace to match the ref to the right handler and
/// an ident to actually uniquely identify it.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct QueueRef([u8; 40]);

impl QueueRef {
    pub fn new(ns: QueueNamespace, sel: QueueIdent) -> Self {
        let mut buf = [0; QUEUE_REF_LEN];
        (&mut buf[..QUEUE_NS_LEN]).copy_from_slice(&ns.0[..]);
        (&mut buf[QUEUE_NS_LEN..]).copy_from_slice(&sel.0[..]);
        Self(buf)
    }

    pub fn namespace_raw(&self) -> [u8; QUEUE_NS_LEN] {
        let mut buf = [0; QUEUE_NS_LEN];
        buf.copy_from_slice(&self.0[..QUEUE_NS_LEN]);
        buf
    }

    pub fn namespace(&self) -> QueueNamespace {
        QueueNamespace(self.namespace_raw())
    }

    pub fn selector_raw(&self) -> [u8; QUEUE_IDENT_LEN] {
        let mut buf = [0; QUEUE_IDENT_LEN];
        buf.copy_from_slice(&self.0[..QUEUE_IDENT_LEN]);
        buf
    }

    pub fn selector(&self) -> QueueIdent {
        QueueIdent(self.selector_raw())
    }

    /// Returns the two parts of the ref.
    pub fn split(&self) -> (QueueNamespace, QueueIdent) {
        (self.namespace(), self.selector())
    }
}

impl fmt::Debug for QueueRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (ns, sel) = self.split();
        write!(f, "{ns:?}:{sel:?}")
    }
}

/// Queue namespace, used to separate different contexts.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct QueueNamespace([u8; QUEUE_NS_LEN]);

impl QueueNamespace {
    pub fn from_str(s: &str) -> Option<QueueNamespace> {
        let sbuf = s.as_bytes();
        if sbuf.len() > QUEUE_NS_LEN {
            return None;
        }

        let mut buf = [0; QUEUE_NS_LEN];
        (&mut buf[..sbuf.len()]).copy_from_slice(sbuf);
        Some(Self(buf))
    }

    pub fn inner(&self) -> &[u8; QUEUE_NS_LEN] {
        &self.0
    }

    pub fn into_inner(&self) -> &[u8; QUEUE_NS_LEN] {
        &self.0
    }

    /// Tries to parse the namespace as a string, if possible.
    pub fn as_str(&self) -> Option<&str> {
        for i in 0..QUEUE_NS_LEN {
            let len = QUEUE_NS_LEN - i;
            let buf = &self.0[..len];

            let Ok(s) = std::str::from_utf8(buf) else {
                continue;
            };

            return Some(s);
        }

        None
    }
}

impl fmt::Debug for QueueNamespace {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.as_str() {
            Some(s) => f.write_str(s),
            None => {
                // TODO avoid alloc
                let h = hex::encode(&self.0);
                f.write_str(&h)
            }
        }
    }
}

/// External queue identifier, used to actually uniquely identify a queue.
///
/// This should actually be a global identifier, although it doesn't have to
/// mean anything specific.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct QueueIdent([u8; QUEUE_IDENT_LEN]);

impl QueueIdent {
    pub fn new(buf: [u8; QUEUE_IDENT_LEN]) -> Self {
        Self(buf)
    }

    /// A queue ident with all zeros.  This isn't meant to be a real ident,
    /// it's used when doing queries for queue IDs.
    pub fn zero() -> Self {
        Self::new([0; QUEUE_IDENT_LEN])
    }

    pub fn inner(&self) -> &[u8; QUEUE_IDENT_LEN] {
        &self.0
    }

    pub fn into_inner(&self) -> &[u8; QUEUE_IDENT_LEN] {
        &self.0
    }

    /// Returns the "next" queue ident.  This is only used when computing
    /// selectors.
    pub fn next_ident(&self) -> QueueIdent {
        let mut buf = self.0;

        // Shitty endian increment.  This also nicely wraps around.
        for i in 0..QUEUE_IDENT_LEN {
            let ii = QUEUE_IDENT_LEN - i - 1;

            let (v, wrap) = buf[ii].overflowing_add(1);
            buf[ii] = v;

            if !wrap {
                break;
            }
        }

        QueueIdent::new(buf)
    }
}

impl fmt::Debug for QueueIdent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // TODO avoid alloc
        let h = hex::encode(&self.0);
        f.write_str(&h)
    }
}
